package Test.PracticeReetrantLock;

import sun.misc.Unsafe;

import java.lang.reflect.Field;
import java.util.concurrent.locks.LockSupport;

/**
 * 迷你版可重入锁
 *
 * @author handsome-pot
 * @date 2021/10/25 9:05
 */
public class MiniReentrantLock implements Lock {

    /**
     * 锁是什么？
     * 其实是资源  那么我们通过 state来表示资源的加锁状态
     * 0  表示未加锁状态
     * >0 表示当前lock是加锁状态
     */
    private volatile int state;

    /**
     * ReentrantLock 是一个独占模式的锁
     * 同一时刻有且只有一个线程可以持有，其他线程再未获取到所得时候，会被阻塞
     * 当前获取到锁的线程
     */
    private Thread exclusiveOwnerThread;


    /**
     * 需要连个引用去维护  阻塞对垒
     * 1.head节点 指向队列头节点
     * 2.tail节点 指向队列尾节点
     * head比较特殊，就是表示当前占用锁的线程
     */
    private Node head;

    private Node tail;

    /**
     * 阻塞的线程会被封装成？
     * node节点，然后放入FIFO的对垒
     */
    static final class Node {
        //前置节点引用
        Node prev;
        //后继节点引用
        Node next;
        //封装的线程本身
        Thread thread;

        public Node() {
        }

        public Node(Thread thread) {
            this.thread = thread;
        }
    }


    private void setHead(Node node) {
        this.head = node;
        //设置为null的原因，因为当前node已经是获取锁成功的线程了
        node.thread = null;
        node.prev = null;
    }


    /**
     * 获取锁
     * 假设当前锁被占用，则会阻塞调用者线程，直到抢占到锁为止
     * 模拟公平模式的锁
     * 公平锁，讲究一个先来后到
     * lock的过程是如何的？
     * 1.线程进来之后，state=0  很幸运，可以直接抢占锁
     * 2.线程进来之后，state>0  很不幸，需要把当前线程入队
     */
    @Override
    public void lock() {
        //第一次获取到锁是，将state 设置为 1
        //第 n 次重入的时候， 将state设置为 n
        acquire(1);
    }

    /**
     * 用于竞争资源
     * 1.尝试强制锁，成功则占用锁，并且返回
     * 2.抢占失败，阻塞当前线程
     */
    private void acquire(int arg) {
        if (!tryAcquire(arg)) {
            //抢占锁失败，需要进行更为复杂的逻辑了
            //入队
            Node node = addWaiter();
            //自旋直到获取到锁之后才会出来
            acquireQueued(node, arg);
        }
    }


    /**
     * 抢占锁失败需要做什么事情呢？
     * 1.需要给当前线程封装成node，然后加入到阻塞队列中
     * 2.需要将当前线程park掉，使线程处于挂起状态
     * <p>
     * 唤醒之后需要做什么呢？
     * 1.检查当前node节点，是否为head.next节点
     * ps：head.next 节点是拥有抢占权限的线程，其他的node没有抢占的权限，因为是公平锁
     * 2.抢占
     * 成功：1.将当前node设置为head，将老的head出队操作，返回业务层
     * 失败：2.继续park，等待被唤醒
     * =======>
     * 1.添加到阻塞队列的逻辑  addWaiter()
     * 2.竞争资源的逻辑     acquireQueued()
     */
    private void acquireQueued(Node node, int arg) {
        //只有当前node成功获取到锁之后  才会跳出自旋
        for (; ; ) {

            //什么情况下，当前node会被唤醒之后  去尝试获取锁呢？
            //只有一种情况，当前node是 head 的后继节点，才有这个权限

            Node pred = node.prev;

            //条件1成立：表示当前node就是head节点的后继节点，也就是拥有抢占权限的node
            //条件2成立：表示当前node抢占锁成功
            if (pred == head && tryAcquire(arg)) {
                //进入这里，表示当前线程  竞争锁成功
                //需要做什么呢？
                //1.设置当前head为当前线程的node
                //2.协助 原始 head出队
                setHead(node);
                pred.next = null;
                return;
            }
            System.out.println("线程:" + Thread.currentThread().getName() + ",挂起");
            //将当前线程挂起
            LockSupport.park();
            System.out.println("线程:" + Thread.currentThread().getName() + ",唤醒");
            //什么时候唤醒当前 被 park掉的线程呢？  那肯定就是 unlock了
        }


    }


    /**
     * 当前线程入队
     * 入队之后，返回当前线程对应的node节点
     * <p>
     * addWaiter 方法执行完成之后，保证当前线程已经入队成功
     */
    private Node addWaiter() {
        Node newNode = new Node(Thread.currentThread());

        //如何入队呢？
        //1.找到newNode的前置节点 pred
        //2.更新newNode.pred = 前置节点
        //3.CAS 更新 tail 为 newNode
        //4.更新 pred.next = newNode

        //前置条件：队列已经又等待者Node了，当前node，不是第一个入队的node
        Node pred = tail;
        if (pred != null) {
            newNode.prev = pred;
            //条件成立，说明当前线程成功入队
            if (compareAndSetTail(pred, newNode)) {
                pred.next = newNode;
                return newNode;
            }
        }

        //执行到这里的几种情况？
        //1.tail == null，队列是空队列
        //2.CAS 设置当前 newNode 为tail 时失败了，被其他线程抢先异步了
        enq(newNode);
        return newNode;
    }

    /**
     * 自旋入队
     * 只有成功后会返回
     * <p>
     * 1.tail == null，队列是空队列
     * 2.CAS 设置当前 newNode 为tail 时失败了，被其他线程抢先一步
     */
    private void enq(Node node) {
        for (; ; ) {
            //第一种情况，当前队列为空
            //也就是   当前线程是第一个抢占失败的线程
            //当前持有锁的线程，并没有设置过任何node  所以作为该的第一个后去节点，需要给它擦屁股
            //也就是给当前持有锁的线程  补充一个node  作为head节点  head节点，都代表当前持有锁的线程

            if (tail == null) {
                //条件成立，表示当前线程给  持有所得线程补充  head操作成功
                if (compareAndSetHead(new Node())) {
                    tail = head;
                    //注意：这里并没有直接返回  还会继续自旋
                }
            } else {
                //表示 当前队列 中已经有 node了  这里是一个追加node的过程

                //如何入队呢？
                //1.找到newNode的前置节点 pred
                //2.更新newNode.pred = 前置节点
                //3.CAS 更新 tail 为 newNode
                //4.更新 pred.next = newNode

                //前置条件：队列已经又等待者Node了，当前node，不是第一个入队的node
                Node pred = tail;
                if (pred != null) {
                    node.prev = pred;
                    //条件成立，说明当前线程成功入队
                    if (compareAndSetTail(pred, node)) {
                        pred.next = node;
                        //注意  入队成功之后，要退出自旋
                        return;
                    }
                }
            }
        }
    }


    /**
     * 尝试获取锁，不会阻塞线程
     * true 表示抢占成功
     * false 表示抢占失败
     */
    private boolean tryAcquire(int arg) {
        if (state == 0) {
            //当state=0的时候，是否可以直接强锁呢？
            //不可以的，因为本次模拟的是公平锁，讲究先来后到

            //条件1：!hasQueuePredecessor()  取反之后为true 表示当前线程前面没有等待者线程
            //条件2： compareAndSetState(0, arg) 使用CAS，因为这个方法可能会有多线程并发的情况
            if (!hasQueuePredecessor() && compareAndSetState(0, arg)) {
                //条件都成立，表示抢占锁成功，需要做些什么呢？
                //1.需要将 exclusiveOwnerThread 设置为进入if的线程
                this.exclusiveOwnerThread = Thread.currentThread();
                return true;
            }

            //什么时候会执行到下面这个条件呢？
            //条件成立，表示当前线程就是获取到锁的线程，需要返回true，并且更新state值
        } else if (Thread.currentThread() == this.exclusiveOwnerThread) {
            //由于其中不存在并发，有且只有当前加了锁的线程，才有权限进来修改，所以可以直接set
            int c = getState();
            c = c + arg;
            //需要做越界
            this.state = c;
            return true;
        }
        //什么时候返回false
        //1.CAS加锁失败
        //2.state>0 并且当前线程，不是持有锁的线程
        return false;
    }


    /**
     * true 表示当前线程前面有等待者线程
     * false 当前线程前面没有等待者线程
     * <p>
     * 调用链：
     * Lock -> acquire -> tryAcquire -> hasQueuePredecessor
     * ps: state值为0时  即当前Lock属于无主状态
     * <p>
     * 那么什么时候返回false呢？
     * 1.当前队列为空
     * 2.当前线程为head.next节点时，   head.next在任何时候都有权力去争取一下lock
     */
    private boolean hasQueuePredecessor() {
        Node h = head;
        Node t = tail;
        Node s;
        //条件1：h != t
        //条件成立  表示队列中已经有node节点了  为什么这么说呢?
        //如果head = tail != null 啥情况呢 第一个获取锁失败的线程 会为当前持有锁的线程  补充创建一个head 节点 而这个时候他 不一定入队了
        //如果 head = tail = null 啥情况呢 要么没有node,要么这个node时第一个持有锁的线程,当前线程前面当然没有等待者线程了
        //条件2.1  (s = h.next) == null
        //极端情况： 第一个获取锁失败的线程  会为持有锁的线程 补充创建一个 head 然后再自旋入队  这个时候会执行下面这两个情况
        //      1. cas tail()成功  表示前面已经有线程入队了
        //      2. pred[head].next = node
        //当执行完1  还没有执行到2的时候    (s = h.next) == null  条件成立了
        //这里其实想要表达的时   已经有 head.next节点了  其他线程再过来的时候  就需要返回 true了
        //条件2.2  s.thread != Thread.currentThread()
        //条件成立：表示当前线程 就不是 h.next节点对应的线程  所以返回 true
        //条件不成立： 表示当前线程  就是 h.next 节点对应的线程  需要返回 false  这个线程回去就可以竞争锁了
        return h != t && ((s = h.next) == null || s.thread != Thread.currentThread());
    }

    /**
     * 释放锁
     */
    @Override
    public void unlock() {
        release(1);
    }

    private void release(int arg) {
        //条件成立  表示当前线程已经完全释放锁了
        if (tryRelease(arg)) {
            //需要做什么呢？
            //阻塞队列面还有几个 阻塞的线程 这里需要 唤醒其中的线程
            Node head = this.head;

            //你得先判断有没有等待者，没有就不需要了
            //条件成立  表示后面还有等待者
            if (head.next != null) {
                //公平锁，也就是要唤醒 head.next节点
                unParkSuccessor(head);
            }
        }
    }

    /**
     * 唤醒当前node的下一个节点对应的线程
     */
    private void unParkSuccessor(Node node) {
        Node s = node.next;
        if (s != null && s.thread != null) {
            LockSupport.unpark(s.thread);
        }
    }

    /**
     * 完全释放锁成功  返回true
     * 否则 也就是 说明 state>0 则返回false
     */
    private boolean tryRelease(int arg) {
        int c = getState() - arg;

        if (exclusiveOwnerThread != Thread.currentThread()) {
            throw new RuntimeException("fuck you ! you must get lock");
        }
        //如果执行到这里还存在并发？ 不存在了 因为同一时间有且只有一个线程可以执行下来

        if (c == 0) {
            //完全释放锁成功
            //需要做什么事情呢？
            //1. exclusiveOwnerThread 设置为 null
            //2. 设置state = 0
            exclusiveOwnerThread = null;
            this.state = c;
            return true;
        }
        this.state = c;
        return false;
    }


    public int getState() {
        return state;
    }

    private static final Unsafe unsafe;
    private static final long stateOffset;
    private static final long headOffset;
    private static final long tailOffset;

    static {
        try {
            Field f = Unsafe.class.getDeclaredField("theUnsafe");
            f.setAccessible(true);

            unsafe = (Unsafe) f.get(null);

            stateOffset = unsafe.objectFieldOffset(MiniReentrantLock.class.getDeclaredField("state"));
            headOffset = unsafe.objectFieldOffset(MiniReentrantLock.class.getDeclaredField("head"));
            tailOffset = unsafe.objectFieldOffset(MiniReentrantLock.class.getDeclaredField("tail"));

        } catch (Exception e) {
            throw new Error(e);
        }
    }

    private final boolean compareAndSetHead(Node update) {
        return unsafe.compareAndSwapObject(this, headOffset, null, update);
    }

    private final boolean compareAndSetTail(Node expect, Node update) {
        return unsafe.compareAndSwapObject(this, tailOffset, expect, update);
    }

    private final boolean compareAndSetState(int expect, int update) {
        return unsafe.compareAndSwapObject(this, stateOffset, expect, update);
    }

}